﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="EntityRepository.cs" company="Collaboris Ltd.">
//   Copyright (c) Collaboris Ltd. All rights Reserved.
// </copyright>
// <summary>
//   Encapsulates access to database tables using EF data entities
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace Collaboris.DataAccess.Linq
{
    #region Imports

    using System;
    using System.Collections.Generic;
    using System.Data;
    using System.Data.Objects;
    using System.Data.Objects.DataClasses;
    using System.Linq;
    using System.Linq.Expressions;

    #endregion

    /// <summary>
    /// Encapsulates access to database tables using EF data entities
    /// </summary>
    /// <typeparam name="T">
    /// the data entity used by the repository
    /// </typeparam>
    /// <typeparam name="C">
    /// The criteria class used by the repository
    /// </typeparam>
    public abstract class EntityRepository<T, C> : IEntityRepository<T> where T : EntityObject, IDataEntity, new() where C : ObjectContext, new()
    {
        #region Fields

        /// <summary>
        /// The context.
        /// </summary>
        private readonly C context;

        #endregion

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="EntityRepository{T,C}"/> class.
        /// </summary>
        protected EntityRepository()
        {
            this.context = new C();
            this.TrackChanges = true;
            this.EntitySetName = this.context.GetEntitySetName<T>();
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="EntityRepository{T,C}"/> class. 
        /// We can pass a context that is already been used
        /// this will improve performance
        /// </summary>
        /// <param name="context">
        /// </param>
        protected EntityRepository(C context)
        {
            this.context = context;
            this.TrackChanges = true;
            this.EntitySetName = context.GetEntitySetName<T>();
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets EntitySetName.
        /// </summary>
        public string EntitySetName
        {
            get;
            private set;
        }

        /// <summary>
        /// The context.
        /// </summary>
        public C Context
        {
            get
            {
                return this.context;
            }
        }

        #endregion

        #region Helper methods

        /*
        private static string GetEntityName(Type t)
        {
            return t.Name;
        }
*/

        /// <summary>
        /// The add business object.
        /// </summary>
        /// <param name="businessObject">
        /// The business object.
        /// </param>
        /// <returns>
        /// The add business object.
        /// </returns>
        protected long AddBusinessObject(T businessObject)
        {
            // AddBusinessObject(businessObject);            
            this.Context.AddObject(this.EntitySetName, businessObject);
            this.Context.SaveChanges();
            return businessObject.Id;
        }

        /// <summary>
        /// Updates the business object with changes.
        /// This method assumes that the buisness object is already attached to the context
        /// </summary>
        /// <param name="businessObject">
        /// </param>
        /// <returns>
        /// The update business object.
        /// </returns>
        protected virtual long UpdateBusinessObject(T businessObject)
        {
            // Context.Attach(businessObject);
            // SetAllModified(businessObject, Context); // custom extension method

            // Context.SaveChanges();
            if (businessObject.EntityState == EntityState.Detached)
            {
                this.Context.Attach(businessObject);
                businessObject.SetAllModified(this.Context);
            }

            this.Context.SaveChanges();

            return businessObject.Id;
        }

        #endregion

        #region IEntityRepository methods

        /// <summary>
        /// Loads the data entity with the specified ID
        /// </summary>
        /// <param name="id">
        /// the Id of the data entity to be loaded
        /// </param>
        /// <returns>
        /// the data entity with the requested ID
        /// </returns>
        public virtual T Load(long id)
        {
            return this.Load(e => e.Id == id);
        }

        /// <summary>
        /// Deletes the data entity with the specified id
        /// </summary>
        /// <param name="itemId">
        /// The Id of the data entity to be deleted
        /// </param>
        public virtual void Delete(long itemId)
        {
            T entity = Load(itemId);

            if (entity != null)
            {
                this.Context.DeleteObject(entity);
                this.Context.SaveChanges();
            }
        }

        /// <summary>
        /// Deletes the specified business object
        /// </summary>
        /// <param name="dataEntity">
        /// The data entity to be deleted
        /// </param>
        public virtual void Delete(T dataEntity)
        {
            if (dataEntity != null)
            {
                this.Context.DeleteObject(dataEntity);
                this.Context.SaveChanges();
            }
        }

        /// <summary>
        /// Gets TrackChanges.
        /// Determines if the returned objects will have tracking enabled.
        /// </summary>
        public bool TrackChanges
        {
            get;
            set;
        }

        /// <summary>
        /// Creates a new empty data entity object
        /// </summary>
        /// <returns>
        /// the newly created data entity
        /// </returns>
        public virtual T CreateDataEntity()
        {
            return new T();
        }

        /// <summary>
        /// The load.
        /// </summary>
        /// <param name="filter">
        /// The filter.
        /// </param>
        /// <returns>
        /// </returns>
        public virtual T Load(Expression<Func<T, bool>> filter)
        {
            // throw new System.NotImplementedException();
            return this.CreateQuery(this.TrackChanges).Where(filter).FirstOrDefault();
        }

        /// <summary>
        /// Returns all the data entities in the repository
        /// </summary>
        /// <returns>
        /// </returns>
        public virtual List<T> LoadList()
        {
            return this.CreateQuery(this.TrackChanges).OfType<T>().ToList();
        }

        /// <summary>
        /// Retrieves a collection of business objects conforming with the criteria
        /// </summary>
        /// <param name="query">
        /// </param>
        /// <returns>
        /// </returns>
        public virtual List<T> LoadList(ObjectQuery<T> query)
        {
            MergeOption mergeOption = query.MergeOption;

            if (!this.TrackChanges)
            {
                mergeOption = MergeOption.NoTracking;
            }

            return query.OfType<T>().Execute(mergeOption).ToList();
        }

        /// <summary>
        /// Retrieves a collection of business objects conforming with the criteria
        /// </summary>
        /// <param name="query">
        /// </param>
        /// <returns>
        /// </returns>
        public virtual List<T> LoadList(IQueryable<T> query)
        {
            return query.ToList();
        }

        /*
        /// <summary>
        /// Retrieves a collection of business objects conforming with the criteria
        /// </summary>
        /// <returns></returns>
        public virtual List<T> LoadList(Expression<Func<T, bool>> expression)
        {
            return ObjecQuery.Where(expression);
        }
        */

        /*
        /// <summary>
        /// Retrieves a  single business object conforming with the criteria
        /// </summary>
        /// <param name="criteria"></param>
        /// <returns></returns>
        public virtual T FirstOrDefault(IQueryable<T> criteria)
        {
            return criteria.FirstOrDefault();
        }
        */

        /*
        public static void AttachUpdated(this ObjectContext context, EntityObject objectDetached)
        {

            if (objectDetached.EntityState == EntityState.Detached)
            {

                object currentEntityInDb = null;

                if (context.TryGetObjectByKey(objectDetached.EntityKey, out currentEntityInDb))
                {

                    context.ApplyPropertyChanges(objectDetached.EntityKey.EntitySetName, objectDetached);

                    //(CDLTLL)Apply property changes to all referenced entities in context 

                    context.ApplyReferencePropertyChanges((IEntityWithRelationships)objectDetached,

                                                                                        (IEntityWithRelationships)currentEntityInDb); //Custom extensor method 

                }

                else
                {

                    throw new ObjectNotFoundException();

                }

            }

        }
        */

        /// <summary>
        /// Submits the bussness object to the database
        /// </summary>
        /// <param name="businessObject">
        /// </param>
        /// <returns>
        /// The submit.
        /// </returns>
        public virtual long Submit(T businessObject)
        {

            if (businessObject.EntityState == EntityState.Detached)
            {
                // check if the object exists
                T currentObject = Load(businessObject.Id);

                if (currentObject != null)
                {
                    // object already exists so just update it
                    // Context.ApplyPropertyChanges(EntitySetName, businessObject);
                    return this.UpdateBusinessObject(businessObject);
                }

                return this.AddBusinessObject(businessObject);
            }

            return this.UpdateBusinessObject(businessObject);
        }

        /// <summary>
        /// Create a query for type T.
        /// </summary>
        /// <param name="trackChanges">
        /// The track Changes.
        /// </param>
        public ObjectQuery<T> CreateQuery(bool trackChanges)
        {
            return this.CreateQuery<T>(trackChanges);
        }

        /// <summary>
        /// Create a query for type C.
        /// </summary>
        /// <typeparam name="C">
        /// </typeparam>
        /// <param name="trackChanges">
        /// The track Changes.
        /// </param>
        public ObjectQuery<C> CreateQuery<C>(bool trackChanges)
        {
            ObjectQuery<C> query;

            string entitySetName = this.context.GetEntitySetName<C>();
            query = this.Context.CreateQuery<C>(entitySetName).OfType<C>();
            if (!trackChanges)
            {
                query.MergeOption = MergeOption.NoTracking;
            }

            return query;
        }

        #endregion

        #region Implementation of IDisposable

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        /// <filterpriority>2</filterpriority>
        public void Dispose()
        {
            if (this.Context != null)
            {
                this.Context.Dispose();
            }
        }

        #endregion
    }
}